package com.haima.sage.bigdata.api.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.haima.sage.bigdata.api.entity.api.Api;
import com.haima.sage.bigdata.api.entity.AppSystem;
import com.haima.sage.bigdata.api.entity.auditlog.OperateType;
import com.haima.sage.bigdata.api.frame.SpringContextUtil;
import com.haima.sage.bigdata.api.frame.process.ApiProcess;
import com.haima.sage.bigdata.api.frame.process.ApiProcessFactory;
import com.haima.sage.bigdata.api.service.ApiService;
import com.haima.sage.bigdata.api.service.AuditLogService;
import com.haima.sage.bigdata.api.entity.api.data.ApiData;
import com.haima.sage.bigdata.api.entity.auditlog.ApiType;
import com.haima.sage.bigdata.api.entity.api.data.ApiParam;
import com.haima.sage.bigdata.api.entity.api.ResultType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RestController
public class QueryController extends BaseController {
    private static final Logger LOG = LogManager.getLogger(QueryController.class);
    private final ApiService apiService;

    @Value("${com.haima.bigdata.query.api.max.batch.size}")
    private Integer maxBatchSize = 10;

    @Autowired
    public QueryController(ApiService apiService, AuditLogService auditLogService) {
        super(auditLogService);
        this.apiService = apiService;
    }

    @PostMapping("/api/query/{id}")
    public ResponseEntity<String> postTypeQuery(HttpServletRequest req,
                                                @PathVariable("id") String id,
                                                @RequestParam("sign") String sign,
                                                @RequestBody String body) {
        ObjectMapper mapper = new ObjectMapper();
        Map<?, ?> vo;
        try {
            vo = mapper.readValue(body, Map.class);
        } catch (IOException e) {
            return error(HttpStatus.BAD_REQUEST,
                    "input json format error", "输入的JSON格式错误");
        }
        Long timestamp = getLongValue(vo, "timestamp");
        String appkey = getStringValue(vo, "appkey");
        String appUserId = getStringValue(vo, "appUserId");
        String appUserName = getStringValue(vo, "appUserName");

        ResponseEntity<String> res = validateBase(appkey, appUserId, appUserName, timestamp);
        if (res != null) {
            return res;
        }
        try {
            DataTuple tuple = query(req, vo, id, mapper, appkey);
            if (tuple.res != null) {
                return tuple.res;
            }
            if (!validSign(tuple.bs.getSecret(), req.getRequestURI(), body, sign)) {
                return error(HttpStatus.BAD_REQUEST,
                        "sign failure", "签名错误");
            }
            return execute(mapper, appkey, appUserId, appUserName, tuple.api, tuple.bs, tuple.data, tuple.params);
        } catch (Exception e) {
            LOG.error("api query error", e);
            return error(HttpStatus.INTERNAL_SERVER_ERROR,
                    "api query error", "此API查询出错");
        }

    }

    private static class DataTuple {
        Api api;
        AppSystem bs;
        Map<String, Object> params;
        ApiData data;
        ResponseEntity<String> res;

        public static DataTuple of(ResponseEntity<String> res) {
            DataTuple data = new DataTuple();
            data.res = res;
            return data;
        }

        public static DataTuple of(Api api, AppSystem bs, Map<String, Object> params, ApiData data) {
            DataTuple obj = new DataTuple();
            obj.api = api;
            obj.bs = bs;
            obj.params = params;
            obj.data = data;
            return obj;
        }
    }

    private DataTuple query(HttpServletRequest req, Map<?, ?> vo, String id, ObjectMapper mapper, String appkey) {

        // 此接口是否存在
        Optional<Api> optional = apiService.findById(id);
        if (optional.isPresent()) {
            Api api = optional.get();
            // 可以访问此接口的业务系统
            AppSystem bs = null;
            try {
                bs = isForbidden(appkey, api);
                if (bs == null) {
                    return DataTuple.of(error(HttpStatus.FORBIDDEN,
                            "no power to access API", "没有权限访问此API"));
                }
            } catch (Exception e) {
                return DataTuple.of(error(HttpStatus.INTERNAL_SERVER_ERROR,
                        "business system secret descrypt fail", "业务系统密钥解密失败"));
            }


            ApiData data;
            try {
                data = mapper.readValue(api.getData(), ApiData.class);
            } catch (Exception e) {
                LOG.error("get api data error", e);
                return DataTuple.of(error(HttpStatus.INTERNAL_SERVER_ERROR,
                        "api data config error, not json", "此API配置JSON错误"));
            }
            Map<String, Object> params = new HashMap<>();
            // 分页

            if (data.getParams() != null) {
                for (ApiParam param : data.getParams()) {
                    String obj = getParameter(req, vo, param);
                    if (param.getRequired()) {
                        if (obj == null || obj.length() == 0) {
                            return DataTuple.of(error(HttpStatus.BAD_REQUEST,
                                    "param " + param.getName() + " value empty",
                                    "参数" + param.getName() + "值为空"));
                        }
                    } else if (obj == null || obj.length() == 0) {
                        continue;
                    }
                    if (matchParameter(param, obj)) {
                        params.put(param.getName(), obj);
                    } else {
                        return DataTuple.of(error(HttpStatus.BAD_REQUEST,
                                "param " + param.getName() + " value pattern error",
                                "参数" + param.getName() + "值格式错误"));
                    }
                }
            }
            return DataTuple.of(api, bs, params, data);
        } else {
            return DataTuple.of(error(HttpStatus.NOT_FOUND, "not found", "无此API"));
        }
    }


    private ResponseEntity<String> execute(ObjectMapper mapper, String appkey, String appUserId, String appUserName, Api api, AppSystem bs, ApiData data, Map<String, Object> params) {
        try {
            setDefaultValue(params, api, data);
            ApiProcess service = ApiProcessFactory.getInstance(api, data);
            if (service == null) {
                return error(HttpStatus.INTERNAL_SERVER_ERROR,
                        "api data config item error", "此API配置项错误");
            }
            Object obj = service.execute(params);
            String json = mapper.writeValueAsString(params);
            addAuditLog(appkey, bs.getName(), ApiType.Query, getOperateType(api.getResult()),
                    api.getName(), json, appUserId, appUserName);
            return ok(obj);
        } catch (Exception e) {
            LOG.error("api data execute error", e);
            return error(HttpStatus.INTERNAL_SERVER_ERROR,
                    "api data execute error", "此API执行错误");
        }
    }

    private OperateType getOperateType(ResultType resultType) {
        if (resultType == null) {
            return OperateType.List;
        }
        switch (resultType) {
            case Row:
                return OperateType.Row;
            case Column:
                return OperateType.Column;
            case Page:
                return OperateType.Page;
            default:
                return OperateType.List;
        }
    }


    @GetMapping("/api/query/{id}")
    public ResponseEntity<String> get(HttpServletRequest req,
                                      @PathVariable("id") String id,
                                      @RequestParam("appkey") String appKey,
                                      @RequestParam("sign") String sign,
                                      @RequestParam("timestamp") Long timestamp,
                                      @RequestParam("appUserId") String userId,
                                      @RequestParam("appUserName") String userName) {
        ResponseEntity<String> res = validateBase(appKey, userId, userName, timestamp);
        if (res != null) {
            return res;
        }
        ObjectMapper mapper = new ObjectMapper();
        try {
            DataTuple tuple = query(req, null, id, mapper, appKey);
            if (tuple.res != null) {
                return tuple.res;
            }
            if (!validSign(tuple.bs.getSecret(), req.getRequestURI(), tuple.params, appKey, timestamp, sign, userId, userName)) {
                return error(HttpStatus.BAD_REQUEST,
                        "sign failure", "签名错误");
            }
            return execute(mapper, appKey, userId, userName, tuple.api, tuple.bs, tuple.data, tuple.params);
        } catch (Exception e) {
            LOG.error("api query error", e);
            return error(HttpStatus.INTERNAL_SERVER_ERROR,
                    "api query error", "此API查询出错");
        }
    }

    private void setDefaultValue(Map<String, Object> params, Api api, ApiData data) {
        if (ResultType.Page == api.getResult() || ResultType.List == api.getResult()) {
            if (data.getMaxBatchSize() == null || data.getMaxBatchSize() < 1) {
                data.setMaxBatchSize(maxBatchSize);
            }
        }
    }


    private boolean matchParameter(ApiParam param, String obj) {
        if (param.getRegex() != null && param.getRegex().length() > 0) {
            Pattern pattern = Pattern.compile(param.getRegex());
            Matcher matcher = pattern.matcher(obj);
            return matcher.matches();
        }
        return true;
    }

    private String getParameter(HttpServletRequest req, Map<?, ?> vo, ApiParam param) {
        String obj = null;
        if (vo == null) {
            obj = req.getParameter(param.getName());
        } else {
            Object tmp = vo.get(param.getName());
            if (tmp != null) {
                obj = tmp.toString();
            }
        }
        if (obj != null && param.getTrim()) {
            obj = obj.trim();
        }
        return obj;
    }


    private AppSystem isForbidden(String appkey, Api api) throws Exception {
        if (api.getSystems() != null && api.getSystems().size() > 0) {
            for (AppSystem bs : api.getSystems()) {
                if (appkey.equals(bs.getId())) {
                    decryptSecret(bs);
                    return bs;
                }
            }
        }
        return null;
    }


}
